
/*!
\class QextSerialPort
\version 1.0.0
\author Stefan Sander

A cross-platform serial port class.
This class encapsulates a serial port on both POSIX and Windows systems.  The user will be
notified of errors and possible portability conflicts at run-time by default - this behavior can
be turned off by defining _TTY_NOWARN_ (to turn off all warnings) or _TTY_NOWARN_PORT_ (to turn
off portability warnings) in the project.

\note
On Windows NT/2000/XP this class uses Win32 serial port functions by default.  The user may
select POSIX behavior under NT, 2000, or XP ONLY by defining _TTY_POSIX_ in the project. I can
make no guarantees as to the quality of POSIX support under NT/2000 however.

*/

#include <stdio.h>
#include "qextserialport.h"
#include <objbase.h>
#include <initguid.h>

/*!
\fn QextSerialPort::QextSerialPort()
Default constructor.  Note that the naming convention used by a QextSerialPort constructed with
this constructor will be determined by #defined constants, or lack thereof - the default behavior
is the same as _TTY_LINUX_.  Possible naming conventions and their associated constants are:

\verbatim

Constant         Used By         Naming Convention
----------       -------------   ------------------------
_TTY_WIN_        Windows         COM1, COM2
_TTY_IRIX_       SGI/IRIX        /dev/ttyf1, /dev/ttyf2
_TTY_HPUX_       HP-UX           /dev/tty1p0, /dev/tty2p0
_TTY_SUN_        SunOS/Solaris   /dev/ttya, /dev/ttyb
_TTY_DIGITAL_    Digital UNIX    /dev/tty01, /dev/tty02
_TTY_FREEBSD_    FreeBSD         /dev/ttyd0, /dev/ttyd1
_TTY_LINUX_      Linux           /dev/ttyS0, /dev/ttyS1
<none>           Linux           /dev/ttyS0, /dev/ttyS1
\endverbatim

The object will be associated with the first port in the system, e.g. COM1 on Windows systems.
See the other constructors if you need to use a port other than the first.
*/
QextSerialPort::QextSerialPort()
 : QextBaseType()
{}

/*!
\fn QextSerialPort::QextSerialPort(const QString & name)
Constructs a serial port attached to the port specified by name.
name is the name of the device, which is windowsystem-specific,
e.g."COM1" or "/dev/ttyS0".
*/
QextSerialPort::QextSerialPort(const QString & name)
 : QextBaseType(name)
{}

/*!
\fn QextSerialPort::QextSerialPort(PortSettings const& settings)
Constructs a port with default name and settings specified by the settings parameter.
*/
QextSerialPort::QextSerialPort(PortSettings const& settings)
 : QextBaseType(settings)
{}

/*!
\fn QextSerialPort::QextSerialPort(const QString & name, PortSettings const& settings)
Constructs a port with the name and settings specified.
*/
QextSerialPort::QextSerialPort(const QString & name, PortSettings const& settings)
 : QextBaseType(name, settings)
{}

/*!
\fn QextSerialPort::QextSerialPort(const QextSerialPort& s)
Copy constructor.
*/
QextSerialPort::QextSerialPort(const QextSerialPort& s)
 : QextBaseType(s)
{}

/*!
\fn QextSerialPort& QextSerialPort::operator=(const QextSerialPort& s)
Overrides the = operator.
*/
QextSerialPort& QextSerialPort::operator=(const QextSerialPort& s)
{
    return (QextSerialPort&)QextBaseType::operator=(s);
}

/*!
\fn QextSerialPort::~QextSerialPort()
Standard destructor.
*/
QextSerialPort::~QextSerialPort()
{}

//this is serial port GUID
#ifndef GUID_CLASS_COMPORT
DEFINE_GUID(GUID_CLASS_COMPORT, 0x86e0d1e0L, 0x8089, 0x11d0, 0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73);
#endif

/* Gordon Schumacher's macros for TCHAR -> QString conversions and vice versa */	
#ifdef UNICODE
#define QStringToTCHAR(x)     (wchar_t*) x.utf16()
#define PQStringToTCHAR(x)    (wchar_t*) x->utf16()
#define TCHARToQString(x)     QString::fromUtf16((ushort*)(x))
#define TCHARToQStringN(x,y)  QString::fromUtf16((ushort*)(x),(y))
#else
#define QStringToTCHAR(x)     x.local8Bit().constData()
#define PQStringToTCHAR(x)    x->local8Bit().constData()
#define TCHARToQString(x)     QString::fromLocal8Bit((x))
#define TCHARToQStringN(x,y)  QString::fromLocal8Bit((x),(y))
#endif /*UNICODE*/

//static
QString getRegKeyValue(HKEY key, LPCTSTR property)
{
  DWORD size = 0;
  RegQueryValueEx(key, property, NULL, NULL, NULL, & size);
  BYTE * buff = new BYTE[size];
  if (RegQueryValueEx(key, property, NULL, NULL, buff, & size) == ERROR_SUCCESS) {
    return TCHARToQStringN(buff, size);
    delete [] buff;
  } else {
    qWarning("QextSerialEnumerator::getRegKeyValue: can not obtain value from registry");
    delete [] buff;
    return QString();
  }
}
	
//static
QString getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property)
{
  DWORD buffSize = 0;
  SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, NULL, 0, & buffSize);
  BYTE * buff = new BYTE[buffSize];
  if (!SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, buff, buffSize, NULL))
    qCritical("Can not obtain property: %ld from registry", property); 
  QString result = TCHARToQString(buff);
  delete [] buff;
  return result;
}

//static
void setupAPIScan(QList<QextPortInfo> & infoList)
{
  HDEVINFO devInfo = INVALID_HANDLE_VALUE;
  GUID * guidDev = (GUID *) & GUID_CLASS_COMPORT;
  
  devInfo = SetupDiGetClassDevs(guidDev, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
  if(devInfo == INVALID_HANDLE_VALUE) {
    qCritical("SetupDiGetClassDevs failed. Error code: %ld", GetLastError());
    return;
  }

  //enumerate the devices
  bool ok = true;
  SP_DEVICE_INTERFACE_DATA ifcData;
  ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
  SP_DEVICE_INTERFACE_DETAIL_DATA * detData = NULL;
  DWORD detDataSize = 0;
  DWORD oldDetDataSize = 0;
		
  for (DWORD i = 0; ok; i++) {
    ok = SetupDiEnumDeviceInterfaces(devInfo, NULL, guidDev, i, &ifcData);
    if (ok) {
      SP_DEVINFO_DATA devData = {sizeof(SP_DEVINFO_DATA)};
      //check for required detData size
      SetupDiGetDeviceInterfaceDetail(devInfo, & ifcData, NULL, 0, & detDataSize, & devData);
      //if larger than old detData size then reallocate the buffer
      if (detDataSize > oldDetDataSize) {
	delete [] detData;
	detData = (SP_DEVICE_INTERFACE_DETAIL_DATA *) new char[detDataSize];
	detData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
	oldDetDataSize = detDataSize;
      }
      //check the details
      if (SetupDiGetDeviceInterfaceDetail(devInfo, & ifcData, detData, detDataSize, 
					  NULL, & devData)) {
	// Got a device. Get the details.
	QextPortInfo info;
	info.friendName = getDeviceProperty(devInfo, & devData, SPDRP_FRIENDLYNAME);
	info.physName = getDeviceProperty(devInfo, & devData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME);
	info.enumName = getDeviceProperty(devInfo, & devData, SPDRP_ENUMERATOR_NAME);
	//anyway, to get the port name we must still open registry directly :( ??? 
	//Eh...			
	HKEY devKey = SetupDiOpenDevRegKey(devInfo, & devData, DICS_FLAG_GLOBAL, 0,
					   DIREG_DEV, KEY_READ);
	info.portName = getRegKeyValue(devKey, TEXT("PortName"));
	RegCloseKey(devKey);
	infoList.append(info);
      } else {
	qCritical("SetupDiGetDeviceInterfaceDetail failed. Error code: %ld", GetLastError());
	delete [] detData;
	return;
      }
    } else {
      if (GetLastError() != ERROR_NO_MORE_ITEMS) {
	delete [] detData;
	qCritical("SetupDiEnumDeviceInterfaces failed. Error code: %ld", GetLastError());
	return;
      }
    }
  }
  delete [] detData;
}



